package com.cardone.common.util;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;

import com.cardone.common.action.CopyPropertiesBean2BeanForFormatDateAction;
import com.cardone.common.action.CopyPropertiesBean2BeanForNameAction;
import com.cardone.common.action.CopyPropertiesBean2BeanForNameListAction;
import com.cardone.common.action.CopyPropertiesBean2BeanForParseDateAction;
import com.cardone.common.action.CopyPropertiesBean2MapForFormatDateAction;
import com.cardone.common.action.CopyPropertiesBean2MapForNameAction;
import com.cardone.common.action.CopyPropertiesBean2MapForNameListAction;
import com.cardone.common.action.CopyPropertiesBean2MapForParseDateAction;
import com.cardone.common.action.CopyPropertiesMap2BeanForFormatDateAction;
import com.cardone.common.action.CopyPropertiesMap2BeanForNameAction;
import com.cardone.common.action.CopyPropertiesMap2BeanForNameListAction;
import com.cardone.common.action.CopyPropertiesMap2BeanForParseDateAction;
import com.cardone.common.action.CopyPropertiesMap2MapForFormatDateAction;
import com.cardone.common.action.CopyPropertiesMap2MapForNameAction;
import com.cardone.common.action.CopyPropertiesMap2MapForNameListAction;
import com.cardone.common.action.CopyPropertiesMap2MapForParseDateAction;
import com.cardone.context.ContextHolder;
import com.cardone.context.DictionaryException;
import com.cardone.context.action.Run3Action;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;

/**
 * bean 工具
 *
 * @author yaohaitao
 *
 */
@Slf4j
public class BeanUtils {
	/**
	 * 复制属性
	 *
	 * @param source
	 *          源对象
	 * @param target
	 *          目标对象
	 * @param includeProperties
	 *          包含属性
	 * @throws BeansException
	 */
	public static void copyProperties(final Object source, final Object target, final String... includeProperties) throws BeansException {
		final Class<?> actualEditable = target.getClass();

		final PropertyDescriptor[] targetPds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);

		for (final PropertyDescriptor targetPd : targetPds) {
			if ((targetPd.getWriteMethod() != null) && ((includeProperties == null) || (ArrayUtils.contains(includeProperties, targetPd.getName())))) {
				final PropertyDescriptor sourcePd = org.springframework.beans.BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());

				if ((sourcePd != null) && (sourcePd.getReadMethod() != null)) {
					final Method readMethod = sourcePd.getReadMethod();

					BeanUtils.methodSetAccessibleTrue(readMethod);

					try {
						final Object value = readMethod.invoke(source);

						final Method writeMethod = targetPd.getWriteMethod();

						BeanUtils.methodSetAccessibleTrue(writeMethod);

						writeMethod.invoke(target, value);
					} catch (final IllegalAccessException e) {
						BeanUtils.log.error(e.getMessage(), e);
					} catch (final IllegalArgumentException e) {
						BeanUtils.log.error(e.getMessage(), e);
					} catch (final InvocationTargetException e) {
						BeanUtils.log.error(e.getMessage(), e);
					}
				}
			}
		}
	}

	/**
	 * 复制属性
	 *
	 * @param source
	 *          源对象
	 * @param target
	 *          目标对象
	 * @param parameters
	 *          参数
	 */
	public static void copyProperties(final Object source, final Object target, final Table<String, String, Object> parameters) {
		if ((parameters == null) || parameters.isEmpty()) {
			throw new DictionaryException("parameters不能为空");
		}

		if (source == null) {
			return;
		}

		if (target == null) {
			return;
		}

		String beanIdPrefix = null;

		if (source instanceof Map) {
			beanIdPrefix = "map";
		} else {
			beanIdPrefix = "bean";
		}

		if (target instanceof Map) {
			beanIdPrefix += "2map";
		} else {
			beanIdPrefix += "2bean";
		}

		for (final String rowKey : parameters.rowKeySet()) {
			final String beanId = StringUtils.join("BeanUtils-copyProperties-", beanIdPrefix, "-", rowKey);

			@SuppressWarnings("unchecked")
			Run3Action<Object, Object, Map<String, Object>> run3Action = ContextHolder.getBean(Run3Action.class, beanId);

			if (run3Action == null) {
				switch (rowKey) {
				case "bean2bean-name":
					run3Action = new CopyPropertiesBean2BeanForNameAction();
					break;
				case "bean2map-name":
					run3Action = new CopyPropertiesBean2MapForNameAction();
					break;
				case "map2bean-name":
					run3Action = new CopyPropertiesMap2BeanForNameAction();
					break;
				case "map2map-name":
					run3Action = new CopyPropertiesMap2MapForNameAction();
					break;
				case "bean2bean-name-list":
					run3Action = new CopyPropertiesBean2BeanForNameListAction();
					break;
				case "bean2map-name-list":
					run3Action = new CopyPropertiesBean2MapForNameListAction();
					break;
				case "map2bean-name-list":
					run3Action = new CopyPropertiesMap2BeanForNameListAction();
					break;
				case "map2map-name-list":
					run3Action = new CopyPropertiesMap2MapForNameListAction();
					break;
				case "bean2bean-format-date":
					run3Action = new CopyPropertiesBean2BeanForFormatDateAction();
					break;
				case "bean2map-format-date":
					run3Action = new CopyPropertiesBean2MapForFormatDateAction();
					break;
				case "map2bean-format-date":
					run3Action = new CopyPropertiesMap2BeanForFormatDateAction();
					break;
				case "map2map-format-date":
					run3Action = new CopyPropertiesMap2MapForFormatDateAction();
					break;
				case "bean2bean-parse-date":
					run3Action = new CopyPropertiesBean2BeanForParseDateAction();
					break;
				case "bean2map-parse-date":
					run3Action = new CopyPropertiesBean2MapForParseDateAction();
					break;
				case "map2bean-parse-date":
					run3Action = new CopyPropertiesMap2BeanForParseDateAction();
					break;
				case "map2map-parse-date":
					run3Action = new CopyPropertiesMap2MapForParseDateAction();
					break;
				}
			}

			if (run3Action == null) {
				continue;
			}

			run3Action.run(source, target, parameters.row(rowKey));
		}
	}

	/**
	 * 比较两个对象,得到属性值不相等的属性名称集合
	 *
	 * @param newObject
	 *          新对象
	 * @param oldObject
	 *          旧对象
	 * @param useProperties
	 *          使用属性集合
	 *
	 * @return 修改属性集合
	 *
	 * @throws BeansException
	 */
	public static String[] diffProperties(final Object newObject, final Object oldObject, final String... useProperties) throws BeansException {
		final Class<?> actualEditable = oldObject.getClass();

		final PropertyDescriptor[] oldPds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);

		final List<String> usePropertieList = Lists.newArrayList();

		for (final PropertyDescriptor oldPd : oldPds) {
			if ((oldPd.getReadMethod() == null) || !ArrayUtils.contains(useProperties, oldPd.getName())) {
				continue;
			}

			final PropertyDescriptor newPd = org.springframework.beans.BeanUtils.getPropertyDescriptor(newObject.getClass(), oldPd.getName());

			if ((newPd == null) || (newPd.getReadMethod() == null)) {
				continue;
			}

			final Method newReadMethod = newPd.getReadMethod();

			BeanUtils.methodSetAccessibleTrue(newReadMethod);

			try {
				final Object newValue = newReadMethod.invoke(newObject);

				final Method oldReadMethod = oldPd.getReadMethod();

				BeanUtils.methodSetAccessibleTrue(oldReadMethod);

				final Object oldValue = oldReadMethod.invoke(oldObject);

				if (!Objects.equal(newValue, oldValue)) {
					usePropertieList.add(oldPd.getName());
				}
			} catch (final IllegalAccessException e) {
				BeanUtils.log.error(e.getMessage(), e);
			} catch (final IllegalArgumentException e) {
				BeanUtils.log.error(e.getMessage(), e);
			} catch (final InvocationTargetException e) {
				BeanUtils.log.error(e.getMessage(), e);
			}
		}

		return usePropertieList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
	}

	private static void methodSetAccessibleTrue(final Method readMethod) {
		if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
			readMethod.setAccessible(true);
		}
	}
}